home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Mail / pine3.92 / pico / dos_gen.c < prev    next >
C/C++ Source or Header  |  1996-03-14  |  16KB  |  775 lines

  1. /*
  2.  * $Id: dos_gen.c,v 4.19 1996/03/15 07:41:11 hubert Exp $
  3.  *
  4.  * Program:    Operating system dependent routines - MS DOS Generic
  5.  *
  6.  *
  7.  * Michael Seibel
  8.  * Networks and Distributed Computing
  9.  * Computing and Communications
  10.  * University of Washington
  11.  * Administration Builiding, AG-44
  12.  * Seattle, Washington, 98195, USA
  13.  * Internet: mikes@cac.washington.edu
  14.  *
  15.  * Please address all bugs and comments to "pine-bugs@cac.washington.edu"
  16.  *
  17.  *
  18.  * Pine and Pico are registered trademarks of the University of Washington.
  19.  * No commercial use of these trademarks may be made without prior written
  20.  * permission of the University of Washington.
  21.  * 
  22.  * Pine, Pico, and Pilot software and its included text are Copyright
  23.  * 1989-1996 by the University of Washington.
  24.  * 
  25.  * The full text of our legal notices is contained in the file called
  26.  * CPYRIGHT, included with this distribution.
  27.  *
  28.  *
  29.  * Notes:
  30.  *     - This file should contain the cross section of functions useful
  31.  *       in both DOS and Windows ports of pico.
  32.  *
  33.  */
  34.  
  35. #ifdef    WIN32
  36. #include <io.h>
  37. #endif
  38.  
  39.  
  40. /*
  41.  * picosigs - Install any handlers for the signals we're interested
  42.  *          in catching.
  43.  */
  44. picosigs()
  45. {
  46.     /* no op */
  47. }
  48.  
  49.  
  50. /*
  51.  * Useful definitions...
  52.  */
  53. #ifdef    MOUSE
  54. static int mexist = 0;            /* is the mouse driver installed? */
  55. static int nbuttons;            /* number of buttons on the mouse */
  56. static unsigned mnoop;
  57. #endif
  58. static unsigned char okinfname[32] = {
  59.       0,    0,             /* ^@ - ^G, ^H - ^O  */
  60.       0,    0,            /* ^P - ^W, ^X - ^_  */
  61.       0x80, 0x17,        /* SP - ' ,  ( - /   */
  62.       0xff, 0xe0,        /*  0 - 7 ,  8 - ?   */
  63.       0x7f, 0xff,        /*  @ - G ,  H - O   */
  64.       0xff, 0xe9,        /*  P - W ,  X - _   */
  65.       0x7f, 0xff,        /*  ` - g ,  h - o   */
  66.       0xff, 0xf6,        /*  p - w ,  x - DEL */
  67.       0,    0,             /*  > DEL   */
  68.       0,    0,            /*  > DEL   */
  69.       0,    0,             /*  > DEL   */
  70.       0,    0,             /*  > DEL   */
  71.       0,    0             /*  > DEL   */
  72. };
  73.  
  74.  
  75. /*
  76.  * fallowc - returns TRUE if c is allowable in filenames, FALSE otw
  77.  */
  78. fallowc(c)
  79. int c;
  80. {
  81.     return(okinfname[c>>3] & 0x80>>(c&7));
  82. }
  83.  
  84.  
  85. #ifdef    MOUSE
  86. /*
  87.  * end_mouse - a no-op on DOS/Windows
  88.  */
  89. void
  90. end_mouse()
  91. {
  92. }
  93.  
  94.  
  95. /*
  96.  * mouseexist - function to let outsiders know if mouse is turned on
  97.  *              or not.
  98.  */
  99. mouseexist()
  100. {
  101.     return(mexist);
  102. }
  103. #endif    /* MOUSE */
  104.  
  105.  
  106. /*
  107.  * fexist - returns TRUE if the file exists, FALSE otherwise
  108.  */
  109. fexist(file, m, l)
  110. char *file, *m;
  111. long *l;
  112. {
  113.     struct stat    sbuf;
  114.  
  115.     if(l != NULL)
  116.       *l = 0L;
  117.  
  118.     if(stat(file, &sbuf) < 0){
  119.     if(errno == ENOENT)            /* File not found */
  120.       return(FIOFNF);
  121.     else
  122.       return(FIOERR);
  123.     }
  124.  
  125.     if(l != NULL)
  126.       *l = sbuf.st_size;
  127.  
  128.     if(sbuf.st_mode & S_IFDIR)
  129.       return(FIODIR);
  130.     else if(*m == 't')                /* no links, just say yes */
  131.       return(FIOSUC);
  132.  
  133.     if(m[0] == 'r')                /* read access? */
  134.       return((S_IREAD & sbuf.st_mode) ? FIOSUC : FIONRD);
  135.     else if(m[0] == 'w')            /* write access? */
  136.       return((S_IWRITE & sbuf.st_mode) ? FIOSUC : FIONWT);
  137.     else if(m[0] == 'x')            /* execute access? */
  138.       return((S_IEXEC & sbuf.st_mode) ? FIOSUC : FIONEX);
  139.     return(FIOERR);                /* what? */
  140. }
  141.  
  142.  
  143. /*
  144.  * isdir - returns true if fn is a readable directory, false otherwise
  145.  *         silent on errors (we'll let someone else notice the problem;)).
  146.  */
  147. isdir(fn, l)
  148. char *fn;
  149. long *l;
  150. {
  151.     struct stat sbuf;
  152.  
  153.     if(l)
  154.       *l = 0;
  155.  
  156.     if(stat(fn, &sbuf) < 0)
  157.       return(0);
  158.  
  159.     if(l)
  160.       *l = sbuf.st_size;
  161.  
  162.     return(sbuf.st_mode & S_IFDIR);
  163. }
  164.  
  165.  
  166. /*
  167.  * gethomedir - returns the users home directory
  168.  *              Note: home is malloc'd for life of pico
  169.  */
  170. char *gethomedir(l)
  171. int *l;
  172. {
  173.     static char *home = NULL;
  174.     static short hlen = 0;
  175.  
  176.     if(home == NULL){
  177.     char buf[NLINE];
  178.     sprintf(buf, "%c:\\", _getdrive() + 'A' - 1);
  179.     hlen = strlen(buf);
  180.     if((home=(char *)malloc(((size_t)hlen + 1) * sizeof(char))) == NULL){
  181.         emlwrite("Problem allocating space for home dir", NULL);
  182.         return(0);
  183.     }
  184.     strcpy(home, buf);
  185.     }
  186.  
  187.     if(l)
  188.       *l = hlen;
  189.  
  190.     return(home);
  191. }
  192.  
  193.  
  194. /*
  195.  * homeless - returns true if given file does not reside in the current
  196.  *            user's home directory tree. 
  197.  */
  198. homeless(f)
  199. char *f;
  200. {
  201.     char *home;
  202.     int   len;
  203.  
  204.     home = gethomedir(&len);
  205.     return(strncmp(home, f, len));
  206. }
  207.  
  208.  
  209. /*
  210.  * errstr - return system error string corresponding to given errno
  211.  *          Note: strerror() is not provided on all systems, so it's 
  212.  *          done here once and for all.
  213.  */
  214. char *errstr(err)
  215. int err;
  216. {
  217.     return((err >= 0 && err < sys_nerr) ? sys_errlist[err] : NULL);
  218. }
  219.  
  220.  
  221. /*
  222.  * getfnames - return all file names in the given directory in a single 
  223.  *             malloc'd string.  n contains the number of names
  224.  */
  225. char *getfnames(dn, pat, n, e)
  226. char *dn, *pat, *e;
  227. int  *n;
  228. {
  229.     int status;
  230.     long l;
  231.     char *names, *np, *p;
  232.     char buf[NLINE];
  233.     struct stat sbuf;
  234. #ifdef    WIN32
  235.     struct _finddata_t dbuf;
  236.     long   findrv;
  237. #else
  238.     struct find_t dbuf;                    /* opened directory */
  239. #endif
  240.  
  241.     *n = 0;
  242.  
  243.     if(stat(dn, &sbuf) < 0){
  244.     if(e)
  245.       sprintf(e, "\007Dir \"%s\": %s", dn, strerror(errno));
  246.  
  247.     return(NULL);
  248.     } 
  249.     else{
  250.     l = sbuf.st_size;
  251.     if(!(sbuf.st_mode & S_IFDIR)){
  252.         if(e)
  253.           sprintf(e, "\007Not a directory: \"%s\"", dn);
  254.  
  255.         return(NULL);
  256.     }
  257.     }
  258.  
  259.     if((names=(char *)malloc(sizeof(char)*3072)) == NULL){
  260.     if(e)
  261.     sprintf(e, "\007Can't malloc space for file names", NULL);
  262.  
  263.     return(NULL);
  264.     }
  265.  
  266.     np = names;
  267.  
  268.     strcpy(buf, dn);
  269.     sprintf(buf, "%s%s%s*%s%s", dn,
  270.         (dn[strlen(dn)-1] == '\\') ? "\\" : "",
  271.         (pat && *pat) ? pat : "",
  272.         (pat && *pat && strchr(pat, '.')) ? "" : ".",
  273.         (pat && *pat && strchr(pat, '.')) ? "" : "*");
  274. #ifdef    WIN32
  275.     if((findrv = _findfirst(buf, &dbuf)) < 0){
  276. #else
  277.     if(_dos_findfirst(buf, _A_NORMAL|_A_SUBDIR, &dbuf) != 0){
  278. #endif
  279.     if(e)
  280.       sprintf(e, "Can't find first file in \"%s\"", dn);
  281.  
  282.     free((char *) names);
  283.     return(NULL);
  284.     }
  285.  
  286.     do{
  287.     (*n)++;
  288.     p = dbuf.name;
  289.     while((*np++ = *p++) != '\0')
  290.       ;
  291.     }
  292. #ifdef    WIN32
  293.     while(_findnext(findrv, &dbuf) == 0);
  294.     _findclose(findrv);
  295. #else
  296.     while(_dos_findnext(&dbuf) == 0);
  297. #endif
  298.  
  299.     return(names);
  300. }
  301.  
  302.  
  303. /*
  304.  * fioperr - given the error number and file name, display error
  305.  */
  306. void
  307. fioperr(e, f)
  308. int  e;
  309. char *f;
  310. {
  311.     switch(e){
  312.       case FIOFNF:                /* File not found */
  313.     emlwrite("\007File \"%s\" not found", f);
  314.     break;
  315.       case FIOEOF:                /* end of file */
  316.     emlwrite("\007End of file \"%s\" reached", f);
  317.     break;
  318.       case FIOLNG:                /* name too long */
  319.     emlwrite("\007File name \"%s\" too long", f);
  320.     break;
  321.       case FIODIR:                /* file is a directory */
  322.     emlwrite("\007File \"%s\" is a directory", f);
  323.     break;
  324.       case FIONWT:
  325.     emlwrite("\007Write permission denied: %s", f);
  326.     break;
  327.       case FIONRD:
  328.     emlwrite("\007Read permission denied: %s", f);
  329.     break;
  330.       case FIONEX:
  331.     emlwrite("\007Execute permission denied: %s", f);
  332.     break;
  333.       default:
  334.     emlwrite("\007File I/O error: %s", f);
  335.     }
  336. }
  337.  
  338.  
  339. /*
  340.  * pfnexpand - pico's function to expand the given file name if there is 
  341.  *           a leading '~'
  342.  */
  343. char *pfnexpand(fn, len)
  344. char *fn;
  345. int  len;
  346. {
  347.     return(fn);
  348. }
  349.  
  350.  
  351. /*
  352.  * fixpath - make the given pathname into an absolute path
  353.  */
  354. fixpath(name, len)
  355. char *name;
  356. int  len;
  357. {
  358.     char file[_MAX_PATH];
  359.     int  dr;
  360.  
  361.     if(!len)
  362.       return(0);
  363.     
  364.     /* return the full path of given file */
  365.     if(isalpha(name[0]) && name[1] == ':'){    /* have drive spec? */
  366.     if(name[2] != '\\'){            /* including path? */
  367.         dr = toupper(name[0]) - 'A' + 1;
  368.         if(_getdcwd(dr, file, _MAX_PATH) != NULL){
  369.         if(file[strlen(file)-1] != '\\')
  370.           strcat(file, "\\");
  371.  
  372.         strcat(file, &name[2]);        /* add file name */
  373.         }
  374.         else
  375.           return(0);
  376.     }
  377.     else
  378.       return(1);        /* fully qualified with drive and path! */
  379.     }
  380.     else if(name[0] == '\\') {            /* no drive spec! */
  381.     sprintf(file, "%c:%s", _getdrive()+'A'-1, name);
  382.     }
  383.     else{
  384.     if(Pmaster && !gmode&MDCURDIR)
  385.       strcpy(file, gmode&MDTREE ? opertree : gethomedir(NULL));
  386.     else if(!_getcwd(file, _MAX_PATH))    /* no qualification */
  387.       return(0);
  388.  
  389.     if(*name){                /* if name, append it */
  390.         strcat(file, "\\");
  391.         strcat(file, name);
  392.     }
  393.     }
  394.  
  395.     strncpy(name, file, len);            /* copy back to real buffer */
  396.     name[len-1] = '\0';                /* tie off just in case */
  397.     return(1);
  398. }
  399.  
  400.  
  401. /*
  402.  * compresspath - given a base path and an additional directory, collapse
  403.  *                ".." and "." elements and return absolute path (appending
  404.  *                base if necessary).  
  405.  *
  406.  *                returns  1 if OK, 
  407.  *                         0 if there's a problem
  408.  *                         new path, by side effect, if things went OK
  409.  */
  410. compresspath(base, path, len)
  411. char *base, *path;
  412. int  len;
  413. {
  414.     register int i;
  415.     int  depth = 0;
  416.     char *p;
  417.     char *stack[32];
  418.     char  pathbuf[NLINE];
  419.  
  420. #define PUSHD(X)  (stack[depth++] = X)
  421. #define POPD()    ((depth > 0) ? stack[--depth] : "")
  422.  
  423.     strcpy(pathbuf, path);
  424.     fixpath(pathbuf, len);
  425.  
  426.     p = pathbuf;
  427.     for(i=0; pathbuf[i] != '\0'; i++){        /* pass thru path name */
  428.     if(pathbuf[i] == C_FILESEP){
  429.         if(p != pathbuf)
  430.           PUSHD(p);                /* push dir entry */
  431.         p = &pathbuf[i+1];            /* advance p */
  432.         pathbuf[i] = '\0';            /* cap old p off */
  433.         continue;
  434.     }
  435.  
  436.     if(pathbuf[i] == '.'){            /* special cases! */
  437.         if(pathbuf[i+1] == '.'            /* parent */
  438.            && (pathbuf[i+2] == C_FILESEP || pathbuf[i+2] == '\0')){
  439.         if(!strcmp(POPD(),""))        /* bad news! */
  440.           return(0);
  441.  
  442.         i += 2;
  443.         p = (pathbuf[i] == '\0') ? "" : &pathbuf[i+1];
  444.         }
  445.         else if(pathbuf[i+1] == C_FILESEP || pathbuf[i+1] == '\0'){
  446.         i++;
  447.         p = (pathbuf[i] == '\0') ? "" : &pathbuf[i+1];
  448.         }
  449.     }
  450.     }
  451.  
  452.     if(*p != '\0')
  453.       PUSHD(p);                    /* get last element */
  454.  
  455.     path[0] = '\0';
  456.     for(i = 0; i < depth; i++){
  457.     strcat(path, S_FILESEP);
  458.     strcat(path, stack[i]);
  459.     }
  460.  
  461.     return(1);                    /* everything's ok */
  462. }
  463.  
  464.  
  465. /*
  466.  * tmpname - return a temporary file name in the given buffer
  467.  */
  468. void
  469. tmpname(name)
  470. char *name;
  471. {
  472.     sprintf(name, "%s.txt", tempnam(getenv("TEMP"), "ae"));
  473. }
  474.  
  475.  
  476. /*
  477.  * Take a file name, and from it
  478.  * fabricate a buffer name. This routine knows
  479.  * about the syntax of file names on the target system.
  480.  * I suppose that this information could be put in
  481.  * a better place than a line of code.
  482.  */
  483. void
  484. makename(bname, fname)
  485. char    bname[];
  486. char    fname[];
  487. {
  488.         register char   *cp1;
  489.         register char   *cp2;
  490.  
  491.         cp1 = &fname[0];
  492.         while (*cp1 != 0)
  493.                 ++cp1;
  494.  
  495.         while (cp1!=&fname[0] && cp1[-1]!='\\')
  496.                 --cp1;
  497.         cp2 = &bname[0];
  498.         while (cp2!=&bname[NBUFN-1] && *cp1!=0 && *cp1!=';')
  499.                 *cp2++ = *cp1++;
  500.         *cp2 = 0;
  501. }
  502.  
  503.  
  504. /*
  505.  * copy - copy contents of file 'a' into a file named 'b'.  Return error
  506.  *        if either isn't accessible or is a directory
  507.  */
  508. copy(a, b)
  509. char *a, *b;
  510. {
  511.     int    in, out, n, rv = 0;
  512.     char   *cb;
  513.     struct stat tsb, fsb;
  514.  
  515.     if(stat(a, &fsb) < 0){        /* get source file info */
  516.     emlwrite("Can't Copy: %s", errstr(errno));
  517.     return(-1);
  518.     }
  519.  
  520.     if(!(fsb.st_mode&S_IREAD)){        /* can we read it? */
  521.     emlwrite("\007Read permission denied: %s", a);
  522.     return(-1);
  523.     }
  524.  
  525.     if((fsb.st_mode&S_IFMT) == S_IFDIR){ /* is it a directory? */
  526.     emlwrite("\007Can't copy: %s is a directory", a);
  527.     return(-1);
  528.     }
  529.  
  530.     if(stat(b, &tsb) < 0){        /* get dest file's mode */
  531.     switch(errno){
  532.       case ENOENT:
  533.         break;            /* these are OK */
  534.       default:
  535.         emlwrite("\007Can't Copy: %s", errstr(errno));
  536.         return(-1);
  537.     }
  538.     }
  539.     else{
  540.     if(!(tsb.st_mode&S_IWRITE)){    /* can we write it? */
  541.         emlwrite("\007Write permission denied: %s", b);
  542.         return(-1);
  543.     }
  544.  
  545.     if((tsb.st_mode&S_IFMT) == S_IFDIR){    /* is it directory? */
  546.         emlwrite("\007Can't copy: %s is a directory", b);
  547.         return(-1);
  548.     }
  549.  
  550.     if(fsb.st_dev == tsb.st_dev && fsb.st_ino == tsb.st_ino){
  551.         emlwrite("\007Identical files.  File not copied", NULL);
  552.         return(-1);
  553.     }
  554.     }
  555.  
  556.     if((in = open(a, _O_RDONLY)) < 0){
  557.     emlwrite("Copy Failed: %s", errstr(errno));
  558.     return(-1);
  559.     }
  560.  
  561.     if((out=creat(b, fsb.st_mode&0xfff)) < 0){
  562.     emlwrite("Can't Copy: %s", errstr(errno));
  563.     close(in);
  564.     return(-1);
  565.     }
  566.  
  567.     if((cb = (char *)malloc(NLINE*sizeof(char))) == NULL){
  568.     emlwrite("Can't allocate space for copy buffer!", NULL);
  569.     close(in);
  570.     close(out);
  571.     return(-1);
  572.     }
  573.  
  574.     while(1){                /* do the copy */
  575.     if((n = read(in, cb, NLINE)) < 0){
  576.         emlwrite("Can't Read Copy: %s", errstr(errno));
  577.         rv = -1;
  578.         break;            /* get out now */
  579.     }
  580.  
  581.     if(n == 0)            /* done! */
  582.       break;
  583.  
  584.     if(write(out, cb, n) != n){
  585.         emlwrite("Can't Write Copy: %s", errstr(errno));
  586.         rv = -1;
  587.         break;
  588.     }
  589.     }
  590.  
  591.     free(cb);
  592.     close(in);
  593.     close(out);
  594.     return(rv);
  595. }
  596.  
  597.  
  598. /*
  599.  * Open a file for writing. Return TRUE if all is well, and FALSE on error
  600.  * (cannot create).
  601.  */
  602. ffwopen(fn)
  603. char    *fn;
  604. {
  605.     extern FILE *ffp;
  606.  
  607.     if ((ffp=fopen(fn, "w")) == NULL) {
  608.         emlwrite("Cannot open file for writing", NULL);
  609.         return (FIOERR);
  610.     }
  611.  
  612.     return (FIOSUC);
  613. }
  614.  
  615.  
  616. /*
  617.  * Close a file. Should look at the status in all systems.
  618.  */
  619. ffclose()
  620. {
  621.     extern FILE *ffp;
  622.  
  623.     if (fclose(ffp) != FALSE) {
  624.         emlwrite("Error closing file", NULL);
  625.         return(FIOERR);
  626.     }
  627.  
  628.     return(FIOSUC);
  629. }
  630.  
  631.  
  632. /*
  633.  * worthit - generic sort of test to roughly gage usefulness of using 
  634.  *           optimized scrolling.
  635.  *
  636.  * note:
  637.  *    returns the line on the screen, l, that the dot is currently on
  638.  */
  639. worthit(l)
  640. int *l;
  641. {
  642.     int i;            /* l is current line */
  643.     unsigned below;        /* below is avg # of ch/line under . */
  644.  
  645.     *l = doton(&i, &below);
  646.     below = (i > 0) ? below/(unsigned)i : 0;
  647.  
  648.     return(below > 3);
  649. }
  650.  
  651.  
  652. /*
  653.  * o_insert - optimize screen insert of char c
  654.  */
  655. o_insert(c)
  656. char c;
  657. {
  658.     return(0);
  659. }
  660.  
  661.  
  662. /*
  663.  * o_delete - optimized character deletion
  664.  */
  665. o_delete()
  666. {
  667.     return(0);
  668. }
  669.  
  670.  
  671. /*
  672.  * pico_new_mail - just checks mtime and atime of mail file and notifies user 
  673.  *               if it's possible that they have new mail.
  674.  */
  675. pico_new_mail()
  676. {
  677.     return(0);
  678. }
  679.  
  680.  
  681.  
  682. /*
  683.  * time_to_check - checks the current time against the last time called 
  684.  *                 and returns true if the elapsed time is > timeout
  685.  */
  686. time_to_check()
  687. {
  688.     static time_t lasttime = 0L;
  689.  
  690.     if(!timeout)
  691.       return(FALSE);
  692.  
  693.     if(time((long *) 0) - lasttime > (time_t)timeout){
  694.     lasttime = time((long *) 0);
  695.     return(TRUE);
  696.     }
  697.     else
  698.       return(FALSE);
  699. }
  700.  
  701.  
  702. /*
  703.  * sstrcasecmp - compare two pointers to strings case independently
  704.  */
  705. sstrcasecmp(s1, s2)
  706. QcompType *s1, *s2;
  707. {
  708.     return(stricmp(*(char **)s1, *(char **)s2));
  709. }
  710.  
  711.  
  712. /*
  713.  * chkptinit -- initialize anything we need to support composer
  714.  *        checkpointing
  715.  */
  716. chkptinit(file, n)
  717.     char *file;
  718.     int   n;
  719. {
  720.     if(!file[0]){
  721.     long gmode_save = gmode;
  722.  
  723.     if(gmode&MDCURDIR)
  724.       gmode &= ~MDCURDIR;  /* so fixpath will use home dir */
  725.  
  726.     strcpy(file, "#picoTM0.txt");
  727.     fixpath(file, NLINE);
  728.     gmode = gmode_save;
  729.     }
  730.     else{
  731.     int l = strlen(file);
  732.  
  733.     if(file[l-1] != '\\'){
  734.         file[l++] = '\\';
  735.         file[l]   = '\0';
  736.     }
  737.  
  738.     strcpy(file + l, "#picoTM0.txt");
  739.     }
  740.  
  741.     if(fexist(file, "r", NULL) == FIOSUC){ /* does file exist? */
  742.     char copy[NLINE];
  743.  
  744.     strcpy(copy, "#picoTM1.txt");
  745.     fixpath(copy, NLINE);
  746.     rename(file, copy);  /* save so we don't overwrite it */
  747.     }
  748.  
  749.     unlink(file);
  750. }
  751.  
  752.  
  753. /*
  754.  * sleep the given number of microseconds
  755.  */
  756. ssleep(s)
  757.     clock_t s;
  758. {
  759.     s += clock();
  760.     while(s > clock())
  761.       ;
  762. }
  763.  
  764.  
  765. /*
  766.  * sleep the given number of seconds
  767.  */
  768. sleep(t)
  769.     int t;
  770. {
  771.     time_t out = (time_t)t + time((long *) 0);
  772.     while(out > time((long *) 0))
  773.       ;
  774. }
  775.